/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Server.domain;

import java.awt.Color;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Ripper
 */
public abstract class Entity implements WorldOccupant
{
    //visual data
    private long lastShownHealing;
    private int lookType;
    private Color[] coloring;
    //end-visual data
    
    //environnement data
    private GameLevel gamelevel;
    private String entityName;
    private double x;
    private double y;
    private int hitboxradius;
    private int team;
    //end-environnement data
    
    //private int facingDirection; //1 = north, 2 = east, 3 = south, 4 = west (is still needed?)
    
    //entity's input values
    private boolean movingNorth;
    private boolean movingEast;
    private boolean movingSouth;
    private boolean movingWest;
    //end-entity's input values
    
    private int walkingSpeed; // (100000 / walkingSpeed) ms it takes to move one square
    private long mayNextWalkAt; // at which point in time he may next move (takes more time to move diagonal)
    
    //combat data
    private boolean attackAble;
    private int hitpoints;
    private int maxHitpoints;
    private double hitpointRegen; //hp regen per 0.25 seconds
    private double hpregenamount;
    private long lastRegenedHp;
    
    private int armor;
    private int physicalDamage;
    private int magicArmor;
    private int magicDamage;
    
    private double criticalChance;
    private double criticalMultiplier;
    
    private int attackSpeed; // (100000 / attackSpeed) ms it takes to perform one attack (200 = attack every half a second.)
    private long mayNextAttackAt; // at which point in time he may next attack
    private double attackDirection;
    
    private double lifestealPercentage;
    private double spellvampPercentage;
    //end-combat data
    
    public Entity(GameLevel gl, String name, int lookTyp, Color[] colors, int team,
                  int moveSpeed, boolean canBeAttacked, int hp, double hpregen, 
                  int baseArmor, int basePhysicalDamage, int baseMagicArmor, int baseMagicDamage, 
                  double critChance, double critMpr, double lifestealPrct, double spellvampPrct,
                  int hitbox)
    {
        setColoring(colors);
        setLookType(lookTyp);
        setEntityName(name);
        x = 0;
        y = 0;
        setHitboxradius(hitbox);
        setTeam(team);
        //setFacingDirection(1);
        setGamelevel(gl);
        setMayNextWalkAt(System.currentTimeMillis());
        setMovingEast(false);
        setMovingNorth(false);
        setMovingSouth(false);
        setMovingWest(false);
        setWalkingSpeed(moveSpeed);
        
        setAttackAble(canBeAttacked);
        setMaxHitpoints(hp);
        setHitpoints(hp);
        setHitpointRegen(hpregen);
        lastRegenedHp = System.currentTimeMillis();
        
        setArmor(baseArmor);
        setPhysicalDamage(basePhysicalDamage);
        setMagicArmor(baseMagicArmor);
        setMagicDamage(baseMagicDamage);
        
        setCriticalChance(critChance);
        setCriticalMultiplier(critMpr);
        
        setLifestealPercentage(lifestealPrct);
        setSpellvampPercentage(spellvampPrct);
        
        lastShownHealing = System.currentTimeMillis();
    }
    public void update()
    {
        hpregen();
        think();
        move();
        specificUpdate();
        
    }
    public abstract void specificUpdate();
    public abstract void think();
    public void hpregen()
    {
        if(getHitpointRegen() > 0 && lastRegenedHp + 25 <= System.currentTimeMillis())
        {
            lastRegenedHp = System.currentTimeMillis();
            
            hpregenamount += getHitpointRegen();
            
            int amountToRegen = (int)hpregenamount;
            hpregenamount -= amountToRegen;
            
            heal(amountToRegen);
        }
    }
    public void move()
    {
        if(canWalkNow())
        {
            boolean movedDiaganol = false;
            boolean movedHorizontalOrVertical = false;
            
            int dx = 0;
            int dy = 0;
            if(movingNorth && movingEast)
            {
                movedDiaganol = true;
                dx = 1;
                dy = -1;
            }
            else if(movingNorth && movingWest)
            {
                movedDiaganol = true;
                dx = -1;
                dy = -1;
            }
            else if(movingSouth && movingEast)
            {
                movedDiaganol = true;
                dx = 1;
                dy = 1;
            }
            else if(movingSouth && movingWest)
            {
                movedDiaganol = true;
                dx = -1;
                dy = 1;
            }
            else if(movingNorth)
            {
                movedHorizontalOrVertical = true;
                dy = -1;
            }
            else if(movingSouth)
            {
                movedHorizontalOrVertical = true;
                dy = 1;
            }
            else if(movingEast)
            {
                movedHorizontalOrVertical = true;
                dx = 1;
            }
            else if(movingWest)
            {
                movedHorizontalOrVertical = true;
                dx = -1;
            }
            
            if(dx != 0 || dy != 0)
            {
                /*List<Point> newPointsOccupied = new ArrayList<Point>();
                
                for(Point p: pointsOccupied)
                {
                    newPointsOccupied.add(new Point(p.x + dx, p.y + dy));
                }
                
                boolean moveApproved = getGamelevel().canMoveTo(newPointsOccupied);
                
                if(moveApproved)
                {
                    pointsOccupied = newPointsOccupied;
                    if(movedHorizontalOrVertical)
                    {
                        setMayNextWalkAt(System.currentTimeMillis() + (100000 / getWalkingSpeed()));
                    }
                    else if(movedDiaganol)
                    {
                        setMayNextWalkAt((long)(System.currentTimeMillis() + (100000 / (getWalkingSpeed() * 1.42))));
                    }
                }*/
                
                double newX = x + dx;
                double newY = y + dy;
                
                boolean moveApproved = getGamelevel().canMoveTo(newX,newY,this);
                
                if(moveApproved)
                {
                    x = newX;
                    y = newY;
                    if(movedHorizontalOrVertical)
                    {
                        setMayNextWalkAt(System.currentTimeMillis() + (100000 / getWalkingSpeed()));
                    }
                    else if(movedDiaganol)
                    {
                        setMayNextWalkAt((long)(System.currentTimeMillis() + (100000 / (getWalkingSpeed() * 1.42))));
                    }
                }
            }
        }
    }
    public void attack(double directionOfAttack) //0-2pi angle
    {
        if(canAttackNow())
        {
            setMayNextAttackAt(System.currentTimeMillis() + (10000 / getAttackSpeed()));
            setAttackDirection(directionOfAttack);
            
            doSpecificAttack();
        }
    }
    public void dealPhysicalDamageToEntity(Entity target, boolean canCrit)
    {
        int atkDmg = getPhysicalDamage();
        if(canCrit && TheRandominator.getRandom().nextInt(100) < getCriticalChance()*100)
        {
            //it's a crit!!
            atkDmg = (int)((double)atkDmg * getCriticalMultiplier());

            //TODO: make a little "crit!" symbol-text-thing pop up on screen.
            getGamelevel().getSession().tellGameSession("showtxteffect:255,0,0,"+getX()+","+getY()+","+" crit!! ");
            //System.out.println("crit!");
        }
        
        int damageDealt = target.takePhysicalDamage(atkDmg);
        int lifeStolen = (int)(getLifestealPercentage() * (double)damageDealt);
        heal(lifeStolen);
    }
    public void dealMagicalDamageToEntity(Entity target)
    {
        int magicPower = getMagicDamage();
        
        int damageDealt = target.takeMagicalDamage(magicPower);
        int spellvamped = (int)(getSpellvampPercentage() * (double)damageDealt);
        heal(spellvamped);
    }
    public abstract void doSpecificAttack();
    public String getLocationData()
    {
        return "x:"+getX()+",y:"+getY()+",mn:"+isMovingNorth()+",ms:"+isMovingSouth()+
                ",me:"+isMovingEast()+",mw:"+isMovingWest()+",hp:"+getHitpointPercentage();
    }
    public String getNecessaryInformation()
    {
        String data = ""+getLocationData()+",looktype:"+lookType;
        
        int i = 0;
        for(Color c: coloring)
        {
            data += ",c"+i+":"+c.getRed()+"="+c.getGreen()+"="+c.getBlue();
            i++;
        }
        
        return data;
    }
    public boolean canWalkNow()
    {
        return (mayNextWalkAt <= System.currentTimeMillis());
    }
    public boolean canAttackNow()
    {
        return (mayNextAttackAt <= System.currentTimeMillis());
    }
    public boolean isAlive()
    {
        return getHitpoints() > 0;
    }
    public void takeTrueDamage(int dmg)
    {
        //TODO: make a lil effect show that says (in red) -dmg! (dmg showing the value) on screen.
        //System.out.println("I, "+getEntityName()+", took "+dmg+" damage!");
        getGamelevel().getSession().tellGameSession("showtxteffect:255,0,0,"+getX()+","+getY()+","+" - "+dmg);
        
        setHitpoints(getHitpoints() - dmg);
    }
    public int takePhysicalDamage(int dmg)
    {
        //return the amount of eventual damage dealt. 
        return takeReducedDamage(dmg, getArmor());
    }
    public int takeMagicalDamage(int dmg)
    {
        //return the amount of eventual damage dealt.
        return takeReducedDamage(dmg, getMagicArmor());
    }
    public int takeReducedDamage(int damage, int reduction)
    {
        double prctReduction = 0;
        if(reduction < 51)
        {
            double prct = ((double)reduction * 2) / 100;
            
            prctReduction = 30 * prct;
        }
        else if(reduction < 566)
        {
            prctReduction = ((-(double)84) + (Math.log((double)reduction)) * ((double)29));
        }
        else
        {
            prctReduction = 99.9;
        }
        
        int damageTaken = (int)((double)damage * (((double)100 - prctReduction)/100));
        
        takeTrueDamage(damageTaken);
        
        return damageTaken;
    }
    public void heal(int amount)
    {
        //System.out.println("I, "+getEntityName()+" have been healed for "+amount);
        if(amount > 25 && lastShownHealing + 500 < System.currentTimeMillis() && getHitpoints() < getMaxHitpoints())
        {
            lastShownHealing = System.currentTimeMillis();
            //TODO: make a lil effect show that says (in green) +amount! (amount showing the value) on screen.
            getGamelevel().getSession().tellGameSession("showtxteffect:0,255,0,"+getX()+","+getY()+","+" + "+amount);
        }
        
        setHitpoints(getHitpoints() + amount);
    }
    public int getHitpointPercentage()
    {
        return (getHitpoints() * 100) / getMaxHitpoints();
    }

    public String getEntityName() { return entityName; }
    public final void setEntityName(String entityName) { this.entityName = entityName; }
    //public int getFacingDirection() { return facingDirection; }
    //public final void setFacingDirection(int facingDirection) { this.facingDirection = facingDirection; }
    public GameLevel getGamelevel() { return gamelevel; }
    public final void setGamelevel(GameLevel gamelevel) { this.gamelevel = gamelevel; }
    public long getMayNextWalkAt() { return mayNextWalkAt; }
    public final void setMayNextWalkAt(long mayNextWalkAt) { this.mayNextWalkAt = mayNextWalkAt; } 
    public boolean isMovingEast() { return movingEast; }
    public final void setMovingEast(boolean movingEast) { this.movingEast = movingEast; }
    public boolean isMovingNorth() { return movingNorth; }
    public final void setMovingNorth(boolean movingNorth) { this.movingNorth = movingNorth; }
    public boolean isMovingSouth() { return movingSouth; }
    public final void setMovingSouth(boolean movingSouth) { this.movingSouth = movingSouth; }
    public boolean isMovingWest() { return movingWest; }
    public final void setMovingWest(boolean movingWest) { this.movingWest = movingWest; }
    public int getWalkingSpeed() { return walkingSpeed; }
    public final void setWalkingSpeed(int walkingSpeed) { this.walkingSpeed = walkingSpeed; }
    public boolean isAttackAble() { return attackAble; }
    public final void setAttackAble(boolean attackAble) { this.attackAble = attackAble; }
    public double getHitpointRegen() { return hitpointRegen; }
    public final void setHitpointRegen(double hitpointRegen) { this.hitpointRegen = hitpointRegen; }
    public int getHitpoints() { return hitpoints; }
    public final void setHitpoints(int hitpoints) 
    { 
        if(hitpoints > getMaxHitpoints())
        {
            setHitpoints(getMaxHitpoints());
        }
        else
        {
            this.hitpoints = hitpoints;
        }
    }
    public int getMaxHitpoints() { return maxHitpoints; }
    public final void setMaxHitpoints(int maxHitpoints) { this.maxHitpoints = maxHitpoints; }
    public int getAttackSpeed() { return attackSpeed; }
    public final void setAttackSpeed(int attackSpeed) { this.attackSpeed = attackSpeed; }
    public long getMayNextAttackAt() { return mayNextAttackAt; }
    public final void setMayNextAttackAt(long mayNextAttackAt) { this.mayNextAttackAt = mayNextAttackAt; }
    public double getAttackDirection() { return attackDirection; }
    public final void setAttackDirection(double attackDirection) { this.attackDirection = attackDirection; }
    public int getArmor() { return armor; }
    public final void setArmor(int armor) { this.armor = armor; }
    public int getMagicArmor() { return magicArmor; }
    public final void setMagicArmor(int magicArmor) { this.magicArmor = magicArmor; }
    public int getMagicDamage() { return magicDamage; }
    public final void setMagicDamage(int magicDamage) { this.magicDamage = magicDamage; }
    public int getPhysicalDamage() { return physicalDamage; }
    public final void setPhysicalDamage(int physicalDamage) { this.physicalDamage = physicalDamage; }
    public double getCriticalChance() { return criticalChance; }
    public final void setCriticalChance(double criticalChance) { this.criticalChance = criticalChance; }
    public double getCriticalMultiplier() { return criticalMultiplier; }
    public final void setCriticalMultiplier(double criticalMultiplier) { this.criticalMultiplier = criticalMultiplier; }
    public double getLifestealPercentage() { return lifestealPercentage; }
    public final void setLifestealPercentage(double lifestealPercentage) { this.lifestealPercentage = lifestealPercentage; }
    public double getSpellvampPercentage() { return spellvampPercentage; }
    public final void setSpellvampPercentage(double spellvampPercentage) { this.spellvampPercentage = spellvampPercentage; }
    public Color[] getColoring() { return coloring; }
    public final void setColoring(Color[] coloring) 
    { 
        this.coloring = coloring; 
        
        if(this.coloring == null)
        {
            this.coloring = new Color[4];
            
            for(int a = 0; a < this.coloring.length;a++)
            {
                this.coloring[a] = new Color(255,0,0);
            }
        }
    }
    public int getLookType() { return lookType; }
    public final void setLookType(int lookType) { this.lookType = lookType; }
    @Override
    public int getHitboxradius() { return hitboxradius; }
    @Override
    public final void setHitboxradius(int hitboxradius) { this.hitboxradius = hitboxradius; }
    @Override
    public double getX() { return x; }
    @Override
    public void setX(double x) { this.x = x; }
    @Override
    public double getY() { return y; }
    @Override
    public void setY(double y) { this.y = y; }
    public int getTeam() { return team; }
    public final void setTeam(int team) { this.team = team; }
    
    
}
